#include "StdAfx.h"
#include "ylSessionControl.h"
#include "ylNodeUtils.h"

using namespace yoctolog_viewer;


SessionControl::SessionControl( void )
{
	InitializeComponent();

	SetSessionData(gcnew SessionData());

	pUpdateMessageLogViewDelegate = gcnew UpdateMessageLogViewDelegate( this, &SessionControl::UpdateMessageLogView );
	pCreateLogItemDelegate = gcnew NetMsgDelegate( this, &SessionControl::CreateLogItemMethod );
	pUpdateInfoBar = gcnew UpdateInfoBarDelegate(this, &SessionControl::UpdateInfoBar);

	m_hTargetProcess = 0;
	m_bShuttingDown = false;

	m_pGroupTreeView->GroupSelected += gcnew GroupTreeView::GroupSelectedDelegate(this, &SessionControl::OnGroupSelected);
}


void SessionControl::SetSessionData(SessionData ^pSessionData)
{
	m_pSessionData = pSessionData;
	m_pGroupTreeView->SetSessionData(m_pSessionData);
	logDataGridView->SetSessionData(m_pSessionData);
}


SessionControl::~SessionControl()
{
	m_bShuttingDown = true;

	if( m_pProcessDataThread )
	{
		while( m_pProcessDataThread->IsAlive ) {}
		delete m_pProcessDataThread;
	}
	if( m_pReceiveDataThread )
	{
		while( m_pReceiveDataThread->IsAlive ) {}
		delete m_pReceiveDataThread;
	}
	if( m_pUpdateSymbolsThread )
	{
		while( m_pUpdateSymbolsThread->IsAlive ) {}
		delete m_pUpdateSymbolsThread;
	}

	if (components)
	{
		delete components;
	}

	delete m_pBuffer;
	delete m_pSessionData;
}


void SessionControl::SaveSession(String ^pFilename)
{
	using namespace System::IO;
	using namespace System::Runtime::Serialization::Formatters::Binary;

	// Opens a file and serializes the object into it in binary format.
	Stream^ stream = File::Open( pFilename, FileMode::Create );


	using namespace System::IO::Compression;

	GZipStream ^pCompressStream = gcnew GZipStream(stream, CompressionMode::Compress);

	BinaryFormatter ^pFormatter = gcnew BinaryFormatter();
	pFormatter->AssemblyFormat = Formatters::FormatterAssemblyStyle::Simple;
	pFormatter->TypeFormat = Formatters::FormatterTypeStyle::TypesWhenNeeded;

	pFormatter->Serialize( pCompressStream, m_pSessionData );
	
	pCompressStream->Close();
	stream->Close();
}


void SessionControl::OpenSession(String ^pFilename)
{
	using namespace System::IO;
	using namespace System::Runtime::Serialization::Formatters::Binary;

	// Delete any old session data
	delete m_pSessionData;

	// Opens a file and serializes the object into it in binary format.
	Stream^ stream = File::Open( pFilename, FileMode::Open );

	using namespace System::IO::Compression;

	GZipStream ^pDecompressStream = gcnew GZipStream(stream, CompressionMode::Decompress);

	BinaryFormatter ^pFormatter = gcnew BinaryFormatter();
	pFormatter->AssemblyFormat = Formatters::FormatterAssemblyStyle::Simple;
	pFormatter->TypeFormat = Formatters::FormatterTypeStyle::TypesWhenNeeded;

	try
	{
		SessionData ^pSessionData = dynamic_cast<SessionData^>( pFormatter->Deserialize( pDecompressStream ) );
		SetSessionData(pSessionData);
	}
	catch(Exception ^e)
	{
		MessageBox::Show(e->StackTrace->ToString());
	}

	stream->Close();
	pDecompressStream->Close();

	m_pGroupTreeView->UpdateGroups();

	UpdateMessageLogView();
	UpdateInfoBar();
}


System::Void SessionControl::OnMouseWheel( System::Object^ sender, System::Windows::Forms::MouseEventArgs ^e )
{
	if( e->Delta > 0 )
	{
		// If we're scrolling upwards, always stop scrolling to bottom
		scrollToBottomButton->Checked = false;
	}
	else
	{
		// If we're scrolling down, determine if we're at the bottom; if so, start scrolling automatically to the bottom
		int iLastRowShown = logDataGridView->FirstDisplayedScrollingRowIndex + logDataGridView->DisplayedRowCount(true);
		if( iLastRowShown >= logDataGridView->Rows->Count - 1 )
		{
			scrollToBottomButton->Checked = true;
		}
	}
}


System::Void SessionControl::OnScroll( System::Object^ sender, System::Windows::Forms::ScrollEventArgs^ e )
{
	ScrollBar ^pScrollBar = (ScrollBar ^)sender;
	if( e->Type == ScrollEventType::Last )
	{
		// If the event type is ScrollEventType::Last, then always start scrolling to bottom
		scrollToBottomButton->Checked = true;
	}
	else
	if( e->Type == ScrollEventType::ThumbTrack )
	{
		if( (e->NewValue > e->OldValue) && (e->NewValue >= pScrollBar->Maximum - pScrollBar->LargeChange) )
		{
			// If we have tracked the scrollbar up above (Maximum - LargeChange), then start scrolling to bottom
			scrollToBottomButton->Checked = true;
		}

		if( (e->NewValue < e->OldValue) && (e->NewValue < pScrollBar->Maximum - pScrollBar->LargeChange) )
		{
			// If we have tracked the scrollbar below (Maximum - LargeChange), then stop scrolling to bottom
			scrollToBottomButton->Checked = false;
		}
	}
	else
	if( e->Type == ScrollEventType::LargeDecrement || e->Type == ScrollEventType::SmallDecrement )
	{
		// If there has been any decrement, stop scrolling to bottom
		scrollToBottomButton->Checked = false;
	}
}


System::Void SessionControl::copyToClipboardButton_Click( System::Object^ sender, System::EventArgs^ e )
{
	
	String ^strClipboard = gcnew String("");

	for( int i = 0; i < logDataGridView->Rows->Count; i++ )
	{
		DataGridViewRow ^pRow = logDataGridView->Rows[i];
		for( int j = 0; j < pRow->Cells->Count; j++ )
		{
			strClipboard += pRow->Cells[j]->Value + " ";
		}
		strClipboard += Environment::NewLine;
	}

	Clipboard::SetDataObject(strClipboard);
}


System::Void SessionControl::OnSelectionChanged( System::Object^ sender, System::EventArgs^ e )
{
	if( logDataGridView->GetMessageGroup() == nullptr )
		return;

	System::Windows::Forms::DataGridViewSelectedRowCollection ^pSelectedRows = logDataGridView->SelectedRows;

	if( pSelectedRows->Count > 0 )
	{
		DataGridViewRow ^pRow = pSelectedRows[0];
		unsigned int uiMessageIndex = logDataGridView->GetMessageGroup()->m_vecMessageList[pRow->Index];
		LogMessage ^pMessage = m_pSessionData->GetMessage(uiMessageIndex);

		UpdateCallstackDataGrid(pMessage);
	}
}


void SessionControl::UpdateCallstackDataGrid(LogMessage ^pMessage)
{
	callstackDataGridView->Rows->Clear();

	// Show or hide the callstack view based on whether we have any data to show
	// NOTE: Disabled for now, doesn't look great
	// 		if( pMessage->m_vecCallstack->Count > 0 )
	// 		{
	// 			tableLayoutPanel1->RowStyles[2]->Height = 125;
	// 		}
	// 		else
	// 		{
	// 			tableLayoutPanel1->RowStyles[2]->Height = 0;
	// 		}

	m_xSymbolsMutex.WaitOne();

	for (int i = 0; i < pMessage->m_vecCallstack->Count; i++ )
	{
		int iMemAddress = pMessage->m_vecCallstack[i];

		array<System::Object^> ^vecRowValues = gcnew array<System::Object^>(2);
		System::String ^strSymbolText = gcnew System::String("Symbol lookup failed or process no longer running");
		System::String ^strFileLineText = gcnew System::String("");

		if( m_pSessionData->m_mapCallstackEntries->ContainsKey(iMemAddress) )
		{
			CallstackEntry ^pCallstackEntry = m_pSessionData->m_mapCallstackEntries[iMemAddress];
			strSymbolText = pCallstackEntry->m_strSymbol;
			strFileLineText = pCallstackEntry->m_strFilename + " (" + Int32(pCallstackEntry->m_uiLineNumber).ToString() + ")";
		}

		vecRowValues[0] = strSymbolText;
		vecRowValues[1] = strFileLineText;
		callstackDataGridView->Rows->Add(vecRowValues);

	}

	m_xSymbolsMutex.ReleaseMutex();
}


System::Void SessionControl::ReceiveData( Object ^pClientObject )
{

	if( !pClientObject )
		return;

	System::Net::Sockets::TcpClient ^pClient = (System::Net::Sockets::TcpClient ^)pClientObject;

	// Get a stream Object* for reading and writing
	System::Net::Sockets::NetworkStream^ stream = pClient->GetStream();
	Int32 iRecvBuf = pClient->ReceiveBufferSize;

	m_pBuffer = gcnew array<Byte>(iRecvBuf);

	while( 1 )
	{
		if( m_bShuttingDown )
			break;

		System::Threading::Thread::Sleep(1);

		if( !stream->DataAvailable )
		{
			continue;
		}

		Int32 iPos = 0;

		Int32 iBytesRead = stream->Read(m_pBuffer, 0, sizeof(Int32));

		Int32 iMsgSize = ( (m_pBuffer[3] << 24) 
			+ (m_pBuffer[2] << 16) 
			+ (m_pBuffer[1] << 8) 
			+ (m_pBuffer[0] ) );

		array<Byte>^ pBuffer = gcnew array<Byte>(iMsgSize);


		Int32 iBytesReadOfMsg = 0;

		while( iBytesReadOfMsg < iMsgSize )
		{
			iBytesRead = stream->Read(pBuffer, iBytesReadOfMsg, iMsgSize - iBytesReadOfMsg);
			iBytesReadOfMsg += iBytesRead;
		}

		m_pSessionData->TotalBytesRead += iBytesReadOfMsg;

		this->Invoke( this->pUpdateInfoBar );

		m_streamMutex.WaitOne();
		m_vecStreamsToProcess.push(pBuffer);
		m_streamMutex.ReleaseMutex();
	}

}


System::Void SessionControl::ProcessData( Object ^client )
{
	while( 1 )
	{
		if( m_bShuttingDown )
			break;

		System::Threading::Thread::Sleep(1);
		
		m_streamMutex.WaitOne();

		if( m_vecStreamsToProcess.empty() )
		{
			m_streamMutex.ReleaseMutex();
			continue;
		}

		array<Byte>^ pBuffer = m_vecStreamsToProcess.front();
		m_vecStreamsToProcess.pop();
		m_streamMutex.ReleaseMutex();

		pin_ptr<System::Byte> pData = &pBuffer[0];
		yoctolog::MemoryStream xMemStream(reinterpret_cast<unsigned char*>(pData), pBuffer->Length);

		int iMemStreamSize = xMemStream.GetUsedSize();
		while( xMemStream.GetPos() < iMemStreamSize )
		{
			unsigned char ucMsgType = xMemStream.GetData()[xMemStream.GetPos()];

			yoctolog::ILogNetMsg_s *pMsg = NULL;
			switch( ucMsgType )
			{
			case yoctolog::LOG_MSG_CREATE_ID:
				pMsg = new yoctolog::LogCreateIDMsg_s();
				break;
			case yoctolog::LOG_MSG_TEXT:
				pMsg = new yoctolog::LogNetMsg_s();
				break;
			case yoctolog::LOG_MSG_PROCESS_INFO:
				pMsg = new yoctolog::LogProcessInfo_s();
				break;
			case yoctolog::LOG_MSG_CREATE_CLASS:
				pMsg = new yoctolog::LogCreateMsgClass_s();
				break;
			}

			if( pMsg )
				pMsg->Deserialize(xMemStream);

			++m_pSessionData->TotalMsgsProcessed;

			if( logDataGridView->InvokeRequired )
			{
				switch( ucMsgType )
				{
				case yoctolog::LOG_MSG_CREATE_ID:
					{
						array<Object^> ^ vecArgs = gcnew array<Object^>(1);
						vecArgs[0] = (Int64)pMsg;

						logDataGridView->Invoke( this->pCreateLogItemDelegate, vecArgs );
					}
					break;
				case yoctolog::LOG_MSG_CREATE_CLASS:
					{
						yoctolog::LogCreateMsgClass_s *pCreateClassMsg = ((yoctolog::LogCreateMsgClass_s*)pMsg);
						LogMessageClass ^pClass = gcnew LogMessageClass();
						pClass->m_pName = gcnew System::String(yoctolog::StringUtils::ToUnicode(pCreateClassMsg->strName).c_str());

						if( !pCreateClassMsg->strTextColor.empty() )
							pClass->m_textColor = System::Drawing::ColorTranslator::FromHtml(gcnew System::String(yoctolog::StringUtils::ToUnicode(pCreateClassMsg->strTextColor).c_str()));
						if( !pCreateClassMsg->strBackgroundColor.empty() )
							pClass->m_bgColor = System::Drawing::ColorTranslator::FromHtml(gcnew System::String(yoctolog::StringUtils::ToUnicode(pCreateClassMsg->strBackgroundColor).c_str()));
						pClass->m_bBold = pCreateClassMsg->bBold;
						m_pSessionData->m_mapMessageClasses[pCreateClassMsg->ucID] = pClass; 
					}
					break;
				case yoctolog::LOG_MSG_PROCESS_INFO:
					{
						m_hTargetProcess = ((yoctolog::LogProcessInfo_s*)pMsg)->hProcess;
						m_pSessionData->ComputerName = gcnew System::String(yoctolog::StringUtils::ToUnicode(((yoctolog::LogProcessInfo_s*)pMsg)->strComputerName).c_str());
					}
					break;
				case yoctolog::LOG_MSG_TEXT:

					{

						unsigned int uiTemplateID = ((yoctolog::LogNetMsg_s*)pMsg)->uiID;

						LogMessageTemplate ^pTemplate = m_pSessionData->GetMessageTemplate(uiTemplateID);

						if( pTemplate )
						{
							LogMessage ^pLogMsg = gcnew LogMessage();
							pLogMsg->m_pTemplate = pTemplate;
							pLogMsg->m_uiID = m_pSessionData->GetNextID();

							for( int i = 0; i < pTemplate->m_strText->Length; i++ )
							{
								if( pTemplate->m_strText[i] == '%' )
								{
									switch( pTemplate->m_strText[i+1] )
									{
									case 'd':
										{
											int iValue = 0;
											xMemStream >> iValue;
											pLogMsg->m_vecParameters->Add(iValue);
										}
										break;
									case 'f':
										{
											float fValue = 0.0f;
											xMemStream >> fValue;
											pLogMsg->m_vecParameters->Add(fValue);
										}
										break;
									case 's':
										{
											std::string strValue;
											xMemStream >> strValue;
											pLogMsg->m_vecParameters->Add(gcnew System::String(yoctolog::StringUtils::ToUnicode(strValue).c_str()));
										}
										break;
									default:
										{
											// By default, treat it as an int
											int iValue = 0;
											xMemStream >> iValue;
											pLogMsg->m_vecParameters->Add(iValue);
										}
										break;
									}
									++i;
								}
							}

							int iNumCallstackEntries = 0;
							xMemStream >> iNumCallstackEntries;

							for( int i = 0; i < iNumCallstackEntries; i++ )
							{
								unsigned int uiMemAddress = 0;
								xMemStream >> uiMemAddress;

								pLogMsg->m_vecCallstack->Add(uiMemAddress);

								m_xSymbolsMutex.WaitOne();
								m_vecSymbolsToResolve.Add(uiMemAddress);
								m_xSymbolsMutex.ReleaseMutex();
							}


							try
							{
								m_pSessionData->AddMessage(pLogMsg);

								// Add it to each group that the template is part of
								for( int i = 0; i < pTemplate->m_vecGroups->Count; i++ )
								{
									pTemplate->m_vecGroups[i]->AddMessage(pLogMsg);
								}
							}
							catch (Exception ^e)
							{
								MessageBox::Show(e->Message);
							}
						}



					}

					break;

				default:
					MessageBox::Show("Invalid message type: " + ucMsgType);
					iMemStreamSize = 0;
					break;
				}

			}

			delete pMsg;
		}


		logDataGridView->Invoke( this->pUpdateMessageLogViewDelegate );

		delete pBuffer;
		m_pSessionData->TotalBytesProcessed += iMemStreamSize;

		this->Invoke( this->pUpdateInfoBar );
	}
}


System::Void SessionControl::OnLoad( System::Object^ sender, System::EventArgs^ e )
{
	{
		// VerticalScrollBar is a private property of the DataGridView, but we need to access it in order to respond correctly to scrolling events.
		// Get it through reflection, and set up the event handler to listen to the Scroll event
		using namespace System::Reflection;

		PropertyInfo ^pi = logDataGridView->GetType()->GetProperty("VerticalScrollBar", BindingFlags::Instance | BindingFlags::NonPublic);
		ScrollBar ^s = nullptr;

		if (pi != nullptr)
			s = (ScrollBar^)pi->GetValue(logDataGridView, nullptr);

		if (s != nullptr)
		{
			s->Scroll += gcnew System::Windows::Forms::ScrollEventHandler(this, &SessionControl::OnScroll);
		}

		logDataGridView->MouseWheel += gcnew MouseEventHandler(this, &SessionControl::OnMouseWheel);
	}
}


void SessionControl::UpdateInfoBar()
{
	connectionLabel->Text = "Client: " + m_pSessionData->ComputerName + " (" + m_pSessionData->RemoteAddress + ")" +
		Environment::NewLine + 
		"Session started: " + m_pSessionData->StartedDateTime;
	
	dataLabel->Text = "Data received: " + Int32(m_pSessionData->TotalBytesRead).ToString() + " bytes" + 
		Environment::NewLine + 
		"Data processed: " + Int32(m_pSessionData->TotalMsgsProcessed).ToString() + " messages (" + Int32(m_pSessionData->TotalBytesProcessed).ToString() + " bytes)";

	//statusLabel->Text = Int32(logDataGridView->FirstDisplayedScrollingRowIndex).ToString() + " " + Int32(logDataGridView->RowCount).ToString();

	m_pGroupTreeView->UpdateGroups();
}


void SessionControl::OnGroupSelected(LogMessageGroup ^pGroup)
{
	logDataGridView->RowCount = 0;
	logDataGridView->SetGroup(pGroup);
	UpdateMessageLogView();
}


void SessionControl::CreateLogItemMethod( Int64 pMsg )
{
	yoctolog::LogCreateIDMsg_s &msg = *((yoctolog::LogCreateIDMsg_s *)pMsg);

	std::wstring strTag = yoctolog::StringUtils::ToUnicode(msg.strTag);
	std::wstring strText = yoctolog::StringUtils::ToUnicode(msg.strText);

	LogMessageTemplate ^pTemplate = gcnew LogMessageTemplate();
	pTemplate->m_strText = gcnew System::String(strText.c_str());

	array<String^> ^vecTagGroups = (gcnew System::String(strTag.c_str()))->Split((gcnew String("&"))->ToCharArray());

	for( int j = 0; j < vecTagGroups->Length; j++ )
	{
		TreeNode ^pNode = m_pGroupTreeView->CreateOrGetNode(vecTagGroups[j]);
		System::String ^strNodePath = GetNodePath(pNode);

		if( !m_pSessionData->m_mapGroups->ContainsKey(strNodePath) )
		{
			m_pSessionData->m_mapGroups[strNodePath] = gcnew LogMessageGroup();
		}

		pTemplate->m_vecGroups->Add(m_pSessionData->m_mapGroups[strNodePath]);

		if( logDataGridView->GetMessageGroup() == nullptr )
			logDataGridView->SetGroup(m_pSessionData->m_mapGroups[strNodePath]);
	}

	if( m_pSessionData->m_mapMessageClasses->ContainsKey(msg.ucMsgClass) )
	{
		pTemplate->m_pMessageClass = m_pSessionData->m_mapMessageClasses[msg.ucMsgClass];
	}
	else
	{
		MessageBox::Show("Invalid message class for message");
	}

	m_pSessionData->AddMessageTemplate(msg.uiID, pTemplate);
}


void SessionControl::UpdateMessageLogView()
{
	if( logDataGridView->GetMessageGroup() == nullptr )
		return;

	try
	{
		bool bScrollToEnd = scrollToBottomButton->Checked;

		logDataGridView->RowCount = logDataGridView->GetMessageGroup()->m_vecMessageList->Count;

		if( bScrollToEnd )
			logDataGridView->FirstDisplayedScrollingRowIndex = logDataGridView->RowCount - 1;
	}
	catch (Exception^ e)
	{
		Console::WriteLine(e->ToString());
	}
}



void SessionControl::AcceptClient(System::Net::Sockets::TcpClient ^pClient)
{
	m_pReceiveDataThread = gcnew System::Threading::Thread(gcnew System::Threading::ParameterizedThreadStart(this, &SessionControl::ReceiveData));
	m_pReceiveDataThread->Start(pClient);

	m_pProcessDataThread = gcnew System::Threading::Thread(gcnew System::Threading::ParameterizedThreadStart(this, &SessionControl::ProcessData));
	m_pProcessDataThread->Start(pClient);

	m_pUpdateSymbolsThread = gcnew System::Threading::Thread(gcnew System::Threading::ParameterizedThreadStart(this, &SessionControl::UpdateSymbols));
	m_pUpdateSymbolsThread->Start();
}


void SessionControl::UpdateSymbols(System::Object ^pObject)
{
	while( 1 )
	{
		if( m_bShuttingDown )
			break;

		// Only update symbols once every second
		System::Threading::Thread::Sleep(1000);

		if( m_vecSymbolsToResolve.Count == 0 || m_hTargetProcess == 0 )
			continue;

		m_xSymbolsMutex.WaitOne();

		for each( unsigned int uiAddress in m_vecSymbolsToResolve )
		{
			std::string strSymbol = SymbolLookup::Instance().GetSymbolFromAddr(m_hTargetProcess, uiAddress);

			std::string strFilename;
			int iLineNumber = 0;
			SymbolLookup::Instance().GetLineNumberFromAddr(m_hTargetProcess, uiAddress, strFilename, iLineNumber);

			System::String ^strSymbolText = gcnew System::String("Symbol lookup failed or process no longer running");
			System::String ^strFileLineText = gcnew System::String("");

			strSymbolText = gcnew System::String(yoctolog::StringUtils::ToUnicode(strSymbol).c_str());
			strFileLineText = gcnew System::String(yoctolog::StringUtils::ToUnicode(strFilename).c_str()) + " (" + Int32(iLineNumber).ToString() + ")";

			CallstackEntry ^pCallstackEntry = gcnew CallstackEntry();
			pCallstackEntry->m_strFilename = gcnew System::String(strFilename.c_str());
			pCallstackEntry->m_strSymbol = gcnew System::String(strSymbol.c_str());
			pCallstackEntry->m_uiLineNumber = iLineNumber;
			m_pSessionData->m_mapCallstackEntries[uiAddress] = pCallstackEntry;
		}

		m_vecSymbolsToResolve.Clear();

		m_xSymbolsMutex.ReleaseMutex();

		SymbolLookup::Instance().CleanUp();

	}
}